package com.jtcoding.auditlog.aspect;

import com.jtcoding.auditlog.annotation.AuditAction;
import com.jtcoding.auditlog.common.JacksonSerializer;
import com.jtcoding.auditlog.common.enums.Action;
import com.jtcoding.auditlog.dao.AuditLogDao;
import com.jtcoding.auditlog.model.AuditLog;
import com.jtcoding.auditlog.model.Company;
import com.jtcoding.auditlog.model.Plan;
import com.jtcoding.auditlog.service.AuditLogService;
import com.jtcoding.auditlog.service.CompanyService;
import com.jtcoding.auditlog.service.PlanService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;

/**
 * @author jason.tang
 * @create 2019-01-21 14:01
 * @description 系统监控
 */

@Slf4j
@Aspect
@Component
public class SystemAudioAspect {

    @Autowired
    private CompanyService companyService;

    @Autowired
    private PlanService planService;

    @Autowired
    private AuditLogService auditLogService;

    /**
     * 拦截DELETE操作，记录被删除的数据
     * @param joinPoint
     */
    @Before(value = "execution(public * com.jtcoding.auditlog.dao..*.delete*(..))")
    public void doBefore(JoinPoint joinPoint) {
        // 获取方法中的参数
        Object[] args = joinPoint.getArgs();
        // 获取该方法上的 @AuditAction注解
        AuditAction audioAction = this.getAudioActionByJoinPoint(joinPoint);
        if (audioAction != null && audioAction.action() == Action.DELETE) {
            Object obj = null;
            String targetTable = audioAction.targetTable();
            switch (targetTable) {
                case "company":
                    int companyNum = (int) args[0];
                    obj = companyService.getCompanyByNum(companyNum);
                    break;
                case "plan":
                    int planNum = (int) args[0];
                    obj = planService.getPlanByNum(planNum);
                    break;
            }
            if (obj != null) {
                this.addAudioLog(obj, AuditLogDao.DELETE, targetTable, null);
            }
        }
    }

    /**
     * 拦截ADD操作，记录新增的数据
     * @param joinPoint
     */
    @After(value = "execution(public * com.jtcoding.auditlog.dao..*.add*(..))")
    public void doAfter(JoinPoint joinPoint) {
        // 获取该方法上的 @AuditAction注解
        AuditAction audioAction = this.getAudioActionByJoinPoint(joinPoint);
        if (audioAction != null && audioAction.action() == Action.ADD) {
            Object obj = joinPoint.getArgs()[0];
            this.addAudioLog(obj, AuditLogDao.ADD, audioAction.targetTable(), null);
        }
    }

    /**
     * 拦截UPDATE操作，记录更新前后的数据
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(value = "execution(public * com.jtcoding.auditlog.dao..*.update*(..))")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        AuditAction audioAction = this.getAudioActionByJoinPoint(pjp);
        Object proceed = null;
        if (audioAction != null && audioAction.action() == Action.UPDATE) {
            String uuid = UUID.randomUUID().toString();
            Object originalObj = null;
            Object arg = pjp.getArgs()[0];
            String targetTable = audioAction.targetTable();
            switch (targetTable) {
                case "company":
                    Company company = (Company) arg;
                    originalObj = companyService.getCompanyByNum(company.getCompanyNum());
                    break;
                case "plan":
                    Plan plan = (Plan) arg;
                    originalObj = planService.getPlanByNum(plan.getPlanNum());
                    break;
            }
            AuditLog auditLog = null;
            if (originalObj != null) {
                // TODO 在执行原方法之前，记录旧数据
                auditLog = this.addAudioLog(originalObj, AuditLogDao.UPDATE, targetTable, null);
            }
            // 执行原方法
            proceed = pjp.proceed();
            // TODO 在执行原方法之后，记录新数据
            if (auditLog != null) {
                this.addAudioLog(arg, AuditLogDao.UPDATE, targetTable, auditLog.getLogNum());
            }
        }
        if (proceed == null) {
            return pjp.proceed();
        }
        return proceed;
    }

    private AuditLog addAudioLog(Object obj, String type, String tableName, Integer srcNum) {
        String jsonString = JacksonSerializer.toJSONString(obj);
        AuditLog audioLog = AuditLog.builder().logData(jsonString).logTableName(tableName)
                .logType(type).srcNum(srcNum).createDateTime(LocalDateTime.now()).build();
        auditLogService.addAuditLog(audioLog);
        return audioLog;
    }

    /**
     * 根据JoinPoint对象获取目标方法上的注解
     * @param joinPoint
     * @return AuditAction
     */
    private AuditAction getAudioActionByJoinPoint(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Method[] methods = joinPoint.getSignature().getDeclaringType().getMethods();

        Method tempMethod = Arrays.stream(methods)
                .filter(m -> m.getName().equals(methodName))
                .filter(m -> Objects.equals(m.getParameterTypes().length, joinPoint.getArgs().length))
                .findFirst()
                .orElse(null);
        if (tempMethod != null) {
            AuditAction audioAction = (AuditAction) Arrays.stream(tempMethod.getDeclaredAnnotations())
                    .filter(a -> a instanceof AuditAction)
                    .findFirst()
                    .orElse(null);
            if (audioAction != null ) {
                return audioAction;
            }
        }
        return null;
    }
}